// 版权归Go作者所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package objabi

import (
	"bytes"
	"flag"
	"fmt"
	"internal/buildcfg"
	"io"
	"io/ioutil"
	"log"
	"os"
	"reflect"
	"sort"
	"strconv"
	"strings"
)

func Flagcount(name, usage string, val *int) {
	flag.Var((*count)(val), name, usage)
}

func Flagfn1(name, usage string, f func(string)) {
	flag.Var(fn1(f), name, usage)
}

func Flagprint(w io.Writer) {
	flag.CommandLine.SetOutput(w)
	flag.PrintDefaults()
}

func Flagparse(usage func()) {
	flag.Usage = usage
	os.Args = expandArgs(os.Args)
	flag.Parse()
}

// expandArgs在提供的切片中展开“response files”参数。
// 
// 一个“response file”参数以“@”开头，其余的
// 参数是一个文件名，带有CR或CRLF分隔的参数。命名文件中的每个
// 参数也可以包含响应文件
// 参数。见第18468期。
// 
// 如果输入中不包含任何响应文件参数，则返回的切片“out”别名。
// 
// TODO:处理不同目录中递归扩展的相对路径？
// 这有规范吗？是否允许相对路径？
func expandArgs(in []string) (out []string) {
	// 在我们看到“@”参数之前，out是零。
	for i, s := range in {
		if strings.HasPrefix(s, "@") {
			if out == nil {
				out = make([]string, 0, len(in)*2)
				out = append(out, in[:i]...)
			}
			slurp, err := ioutil.ReadFile(s[1:])
			if err != nil {
				log.Fatal(err)
			}
			args := strings.Split(strings.TrimSpace(strings.Replace(string(slurp), "\r", "", -1)), "\n")
			for i, arg := range args {
				args[i] = DecodeArg(arg)
			}
			out = append(out, expandArgs(args)...)
		} else if out != nil {
			out = append(out, s)
		}
	}
	if out == nil {
		return in
	}
	return
}

func AddVersionFlag() {
	flag.Var(versionFlag{}, "V", "print version and exit")
}

var buildID string // 由链接器填写

type versionFlag struct{}

func (versionFlag) IsBoolFlag() bool { return true }
func (versionFlag) Get() interface{} { return nil }
func (versionFlag) String() string   { return "" }
func (versionFlag) Set(s string) error {
	name := os.Args[0]
	name = name[strings.LastIndex(name, `/`)+1:]
	name = name[strings.LastIndex(name, `\`)+1:]
	name = strings.TrimSuffix(name, ".exe")

	p := ""

	if s == "goexperiment" {
		// 测试/运行。go使用它来发现完整的
		// 实验标记集。汇报一切。
		p = " X:" + strings.Join(buildcfg.AllExperiments(), ",")
	} else {
		// 如果启用的实验与默认实验不同，则
		// 包括该差异。
		if goexperiment := buildcfg.GOEXPERIMENT(); goexperiment != "" {
			p = " X:" + goexperiment
		}
	}

	// go命令调用-V=full以获取此工具的唯一标识符
	// 。我们假设发布版本足够发布
	// ，但在开发过程中，我们包含了二进制文件的完整
	// build ID，因此，如果编译器被更改并重建
	// ，我们会注意到并重建所有包。
	if s == "full" {
		if strings.HasPrefix(buildcfg.Version, "devel") {
			p += " buildID=" + buildID
		}
	}

	fmt.Printf("%s version %s%s\n", name, buildcfg.Version, p)
	os.Exit(0)
	return nil
}

// 计数是一个标志。价值就像一面旗帜。布尔和一面旗帜。Int.
// 如果用作-name，则递增计数，但-name=x设置计数。
// 用于详细标志-v.
type count int

func (c *count) String() string {
	return fmt.Sprint(int(*c))
}

func (c *count) Set(s string) error {
	switch s {
	case "true":
		*c++
	case "false":
		*c = 0
	default:
		n, err := strconv.Atoi(s)
		if err != nil {
			return fmt.Errorf("invalid count %q", s)
		}
		*c = count(n)
	}
	return nil
}

func (c *count) Get() interface{} {
	return int(*c)
}

func (c *count) IsBoolFlag() bool {
	return true
}

func (c *count) IsCountFlag() bool {
	return true
}

type fn1 func(string)

func (f fn1) Set(s string) error {
	f(s)
	return nil
}

func (f fn1) String() string { return "" }

// 解码参数。
// 
// 此函数公开用于使用并行编码器进行测试。
func DecodeArg(arg string) string {
	// 如果没有编码，则快速路径输出。
	if !strings.ContainsAny(arg, "\\\n") {
		return arg
	}

	// 我们不能使用字符串。构建器，因为这必须在引导下工作。这不应该发生。这里仅有的反斜杠
	var b bytes.Buffer
	var wasBS bool
	for _, r := range arg {
		if wasBS {
			switch r {
			case '\\':
				b.WriteByte('\\')
			case 'n':
				b.WriteByte('\n')
			default:
				// 应该专门编码“\n”和“\\”。
				panic("badly formatted input")
			}
		} else if r == '\\' {
			wasBS = true
			continue
		} else {
			b.WriteRune(r)
		}
		wasBS = false
	}
	return b.String()
}

type debugField struct {
	name string
	help string
	val  interface{} // /*int或*string 
}

type DebugFlag struct {
	tab map[string]debugField
	any *bool

	debugSSA DebugSSA
}

// 调用一个DebugSSA函数来设置A-d ssa/。。。选项
// 如果为零，这些选项将被报告为无效选项。
// 如果DebugSSA返回非空字符串，则该文本将报告为编译器错误。
// 如果阶段为“帮助”，则应打印使用信息并终止流程。
type DebugSSA func(phase, flag string, val int, valString string) string

// NewDebugFlag为debug的字段构造一个DebugFlag，它必须是指向结构的指针。
// 
// debug的每个字段都是一个不同的值，以字段名的小写字母命名。
// 每个字段必须是一个int或string，并且必须有一个'help'结构标记。
// 可能有一个“Any bool”字段，如果设置了任何调试标志，就会设置该字段。
// 
// 返回的标志采用逗号分隔的设置列表。
// 每个设置都是name=value；对于int，name是name=1的缩写。
// 
// 如果debugSSA为非nil，则任何形式为ssa/…的调试标志。。。将
// 传递给debugSSA进行处理。
func NewDebugFlag(debug interface{}, debugSSA DebugSSA) *DebugFlag {
	flag := &DebugFlag{
		tab:      make(map[string]debugField),
		debugSSA: debugSSA,
	}

	v := reflect.ValueOf(debug).Elem()
	t := v.Type()
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		ptr := v.Field(i).Addr().Interface()
		if f.Name == "Any" {
			switch ptr := ptr.(type) {
			default:
				panic("debug.Any must have type bool")
			case *bool:
				flag.any = ptr
			}
			continue
		}
		name := strings.ToLower(f.Name)
		help := f.Tag.Get("help")
		if help == "" {
			panic(fmt.Sprintf("debug.%s is missing help text", f.Name))
		}
		switch ptr.(type) {
		default:
			panic(fmt.Sprintf("debug.%s has invalid type %v (must be int or string)", f.Name, f.Type))
		case *int, *string:
			// 确定
		}
		flag.tab[name] = debugField{name, help, ptr}
	}

	return flag
}

func (f *DebugFlag) Set(debugstr string) error {
	if debugstr == "" {
		return nil
	}
	if f.any != nil {
		*f.any = true
	}
	for _, name := range strings.Split(debugstr, ",") {
		if name == "" {
			continue
		}
		// 显示有关调试选项本身的帮助并退出
		if name == "help" {
			fmt.Print(debugHelpHeader)
			maxLen, names := 0, []string{}
			if f.debugSSA != nil {
				maxLen = len("ssa/help")
			}
			for name := range f.tab {
				if len(name) > maxLen {
					maxLen = len(name)
				}
				names = append(names, name)
			}
			sort.Strings(names)
			// 缩进多行帮助消息。
			nl := fmt.Sprintf("\n\t%-*s\t", maxLen, "")
			for _, name := range names {
				help := f.tab[name].help
				fmt.Printf("\t%-*s\t%s\n", maxLen, name, strings.Replace(help, "\n", nl, -1))
			}
			if f.debugSSA != nil {
				// ssa选项有自己的帮助
				fmt.Printf("\t%-*s\t%s\n", maxLen, "ssa/help", "print help about SSA debugging")
			}
			os.Exit(0)
		}

		val, valstring, haveInt := 1, "", true
		if i := strings.IndexAny(name, "=:"); i >= 0 {
			var err error
			name, valstring = name[:i], name[i+1:]
			val, err = strconv.Atoi(valstring)
			if err != nil {
				val, haveInt = 1, false
			}
		}

		if t, ok := f.tab[name]; ok {
			switch vp := t.val.(type) {
			case nil:
				// 忽略
			case *string:
				*vp = valstring
			case *int:
				if !haveInt {
					log.Fatalf("invalid debug value %v", name)
				}
				*vp = val
			default:
				panic("bad debugtab type")
			}
		} else if f.debugSSA != nil && strings.HasPrefix(name, "ssa/") {
			// 期望表单ssa/phase/flag 
			// /e.g.-d=ssa/generic_cse/time 
			// 
			phase := name[4:]
			if i := strings.Index(phase, "/"); i >= 0 {
				flag = phase[i+1:]
				phase = phase[:i]
			}
			err := f.debugSSA(phase, flag, val, valstring)
			if err != "" {
				log.Fatalf(err)
			}
		} else {
			return fmt.Errorf("unknown debug key %s\n", name)
		}
	}

	return nil
}

const debugHelpHeader = `usage: -d arg[,arg]* and arg is <key>[=<value>]

<key> is one of:

`

func (f *DebugFlag) String() string {
	return ""
}
